﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Microsoft.VisualStudio.Modeling;
using System.Windows.Data;
using CodeDomHelper.Types;
using System.ComponentModel;
using Microsoft.RestrictedUsage.CSharp.Semantics;
using devtm.Editor.TypeSelector;


namespace devtm.AutoMapper
{
    public static class MappingHelper
    {


        private class Rank
        {            
            public MemberInfo Source { get; set; }
            public MemberInfo Destination { get; set; }
            public ItemMap Map { get; set; }

            public bool SourceCreated { get; set; }
        }


        internal static void GenerateMapping(MappingSource source, bool refresh)
        {

            TypeElement left = source.TypeElement;            
            Mapper mapper = source.Mapper;
            TypeElement right = mapper.MappingDestination.TypeElement;

            using (Transaction t = source.Mapper.Store.TransactionManager.BeginTransaction("Update mapping"))
            {

                Dictionary<string, Rank> l = GetSource(mapper, left);
                FindDestination(right, l);

                foreach (Rank r in l.Values)
                {
                    
                    MemberInfo prop1 = null;

                    if (r.Source != null)
                    {
                        prop1 = r.Source;
                        r.Map.SourceName = prop1.Name;

                        Type tt = prop1.GetTypeFrom();
                        SetInfoTypeSource(r.Map, tt);
                        r.Map.NeedMap = ResolveNeedMap(tt);

                        r.Map.SourceType = ItemMap.FindTypeName(prop1);                        
                        if (!prop1.CanRead())
                            r.Map.Enable = false;
                    }
                    else
                    {
                        r.Map.SourceName = string.Empty;
                    }

                    if (r.Destination != null)
                    {

                        prop1 = r.Destination;
                        r.Map.TargetName = prop1.Name;

                        Type tt = prop1.GetTypeFrom();
                        SetInfoTypeTarget(r.Map, prop1.GetTypeFrom());
                        r.Map.NeedMap = ResolveNeedMap(tt);

                        r.Map.TargetType = ItemMap.FindTypeName(prop1);

                        if (!prop1.CanWrite())
                            r.Map.Enable = false;
                    }
                    else
                    {
                        r.Map.TargetName = string.Empty;
                    }


                    if (r.Map.Label.Trim() == "->")
                    {

                        r.Map.Label = "Oups";

                    }

                    if (r.SourceCreated)
                    {
                        FindConverter(r.Map, r.SourceCreated);

                        if (r.Map.Enable)
                            r.Map.Enable = !(String.IsNullOrEmpty(r.Map.SourceName) || String.IsNullOrEmpty(r.Map.TargetName));
                    
                    }
                }

                if (!refresh)
                    FindNotUsedMembers(mapper, l);

                t.Commit();

            }

        }



        public static bool CanRead(this MemberInfo property)
        {

            if (property.MemberType == MemberTypes.Field || property.MemberType != MemberTypes.Method)
                return true;

            else if (property.MemberType != MemberTypes.Property)
                return false;

            return (property as PropertyInfo).CanRead;

        }

        public static bool CanWrite(this MemberInfo property)
        {

            if (property.MemberType == MemberTypes.Field)
                return true;

            else if (property.MemberType != MemberTypes.Property)
                return false;

            return (property as PropertyInfo).CanWrite;

        }
        
        public static Type GetTypeFrom(this MemberInfo member)
        {

            if (member == null)
                return null;

            switch (member.MemberType)
            {

                case MemberTypes.Property:
                    PropertyInfo prop = member as PropertyInfo;
                    return prop.PropertyType;

                case MemberTypes.Field:
                    FieldInfo field = member as FieldInfo;
                    return field.FieldType;

                case MemberTypes.Method:
                      MethodInfo method = member as MethodInfo;
                      return method.ReturnType;

                case MemberTypes.NestedType:
                case MemberTypes.TypeInfo:
                      return member as Type;
               
                case MemberTypes.Constructor:
                case MemberTypes.Custom:
                case MemberTypes.Event:
                case MemberTypes.All:
                default:
                      return null;

            }
            
        }

        public static void SetInfoTypeSource(ItemMap item, Type typeSource)
        {
            item.SourceIsAbstract = typeSource.IsAbstract;
            item.SourceIsAnsiClass = typeSource.IsAnsiClass;
            item.SourceIsArray = typeSource.IsArray;
            item.SourceIsClass = typeSource.IsClass;
            item.SourceIsEnum = typeSource.IsEnum;
            item.SourceIsInterface = typeSource.IsInterface;
            item.SourceIsNested = typeSource.IsNested;
            item.SourceIsPublic = typeSource.IsPublic;
            item.SourceIsValueType = typeSource.IsValueType;
            item.SourceIsSealed = typeSource.IsSealed;
            item.SourceIsSerializable = typeSource.IsSerializable;
            item.SourceIsEnumerable = typeSource.GetInterface("IEnumerable", true) != null;
            item.SourceIsNullable = typeSource.AssemblyQualifiedName.StartsWith("System.Nullable`1[[");

        }

        public static void SetInfoTypeTarget(ItemMap item, Type typeTarget)
        {
            item.TargetIsAbstract = typeTarget.IsAbstract;
            item.TargetIsAnsiClass = typeTarget.IsAnsiClass;
            item.TargetIsArray = typeTarget.IsArray;
            item.TargetIsClass = typeTarget.IsClass;
            item.TargetIsEnum = typeTarget.IsEnum;
            item.TargetIsInterface = typeTarget.IsInterface;
            item.TargetIsNested = typeTarget.IsNested;
            item.TargetIsPublic = typeTarget.IsPublic;
            item.TargetIsValueType = typeTarget.IsValueType;
            item.TargetIsSealed = typeTarget.IsSealed;
            item.TargetIsSerializable = typeTarget.IsSerializable;
            item.TargetIsEnumerable = typeTarget.GetInterface("IEnumerable", true) != null;
            item.TargetIsNullable = typeTarget.AssemblyQualifiedName.StartsWith("System.Nullable`1[[");

        }

        public static void FindConverter(ItemMap item, bool sourceCreated)
        {

            if (item.NeedMap)
            {

                if (item.SourceIsEnumerable)
                {

                    // System.Collections.Generic.IList
                    var t1 = BuildTypeExpression.Analyse(item.TargetType);

                    switch (t1.FullName)
                    {

                        case "System.Collections.Generic.List`1":
                        case "System.Collections.Generic.IList`1":
                            item.TypeCast = CastType.List;
                            return;

                        case "System.Collections.Generic.Collection`1":
                        case "System.Collections.Generic.ICollection`1":
                            item.TypeCast = CastType.Collection;
                            return;

                        case "System.Collections.ObjectModel.ObservableCollection`1":
                            item.TypeCast = CastType.ObservableCollection;
                            return;
                        
                    }
                    
                }

                if (item.SourceName == "this" || item.TargetName == "this")
                    item.TypeCast = CastType.Custom;
                else
                    item.TypeCast = CastType.Map;

                return;
            }
            else if (item.SourceType == item.TargetType)
            {
                item.TypeCast = CastType.None;
                return;
            }

            FindCast(item);

        }

        public static void FindCast(ItemMap item)
        {

            //HashSet<string> namespaces = item.GetMapper().MappingModel.GetNamespaces;
            //namespaces.Add("System.ComponentModel");
            //namespaces.Add("System.Configuration");
            //namespaces.Add("System.Diagnostics.Design");
            //namespaces.Add("System.Security.Authentication.ExtendedProtection");
            List<Type> lst = ContainerTypes.GetFromReference(item.Store, null).ToList();
            lst.AddRange(ContainerTypes.GetFromSolution<Object>(item.Store, null));

            if (item.SourceIsEnum || item.TargetIsEnum)
            {
                if (FindCastEnum(item))
                    return;

                item.TypeCast = CastType.Custom;
            }

            if (FindCastImplicit(item))
                return;

            if (NewMapperHelper.types.Contains(item.SourceType) && NewMapperHelper.types.Contains(item.TargetType))
            {
                item.TypeCast = CastType.Explicit;
                return;
            }

            ///TODO remettre les types converters
            //if (FindCastTypeConverter(item, lst))
            //    return;

            //if (FindCastIValueConverter(item, lst))
            //    return;

            //if (FindCastIConverter(item, lst))
            //    return;

            item.TypeCast = CastType.Custom;


        }



        private static Type[] tbTypeValue = new Type[] { typeof(System.Byte), typeof(System.Double), typeof(System.Int16), typeof(System.Int32), typeof(System.Int64), typeof(System.SByte), typeof(System.Single), typeof(System.UInt16), typeof(System.UInt32), typeof(System.UInt64), typeof(System.String), typeof(System.Char) };

        private static bool FindCastEnum(ItemMap item)
        {

            Type t1 = item.Mapper.MappingModel.FindType(item.SourceType);
            Type t2 = item.Mapper.MappingModel.FindType(item.TargetType);

            Type t = t2 != null && t2.IsEnum ? t1 : t2;

            if (t != null && tbTypeValue.Contains(t))
            {
                item.TypeCast = CastType.Explicit;
                return true;
            }

            return false;
        }

        public static bool FindCastImplicit(ItemMap item)
        {

            Type t1 = Type.GetType(item.SourceType);
            Type t2 = Type.GetType(item.TargetType);

            if (t1 != null && t2 != null
                && t1.IsPrimitive && t2.IsPrimitive
                && t1 != typeof(System.Boolean) && t2 != typeof(System.Boolean)
                && t1 != typeof(System.IntPtr) && t2 != typeof(System.IntPtr)
                && t1 != typeof(System.UIntPtr) && t2 != typeof(System.UIntPtr))
            {
                item.TypeCast = CastType.Explicit;
                return true;
            }
                
            return FindCastImplicitForClass(item);
        }

        private static bool FindCastImplicitForClass(ItemMap item)
        {
            Type s1 = Type.GetType(item.SourceType);

            if (s1 != null)
            {
                // recupere les methodes speciales
                MethodInfo[] ms = s1.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(c => c.Attributes == (MethodAttributes.FamANDAssem | MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName)).ToArray();

                foreach (MethodInfo m in ms)
                    if (m.Name == "op_Implicit")
                        if (TestMethod(m, item, CastType.Implicit))
                            return true;

                foreach (MethodInfo m in ms)
                    if (m.Name == "op_Explicit")
                        if (TestMethod(m, item, CastType.Explicit))
                            return true;
            }

            return false;
        }

        private static bool TestMethod(MethodInfo m, ItemMap item, CastType c)
        {

            ParameterInfo[] p = m.GetParameters();
            if (p.Length != 1)
                return false;

            if (TestType(p[0].ParameterType, m.ReturnType, item.SourceType, item.TargetType))
            {
                item.TypeCast = c;
                return true;
            }

            return false;
        }

        private static bool TestType(Type type1, Type type2, string source, string target)
        {

            string s = ItemMap.FindTypeName(type1);
            if (s != source && s != target)
                return false;

            s = ItemMap.FindTypeName(type2);
            if (s != source && s != target)
                return false;

            return true;
        }

        public static bool FindCastIConverter(ItemMap item, List<Type> lst)
        {

            Type filtre = typeof(Verbatim.CustomConverters.IConverter);

            foreach (Type t1 in lst.Where(c => c.IsOfType(false, filtre)))
            {

                Type[] t2 = t1.GetInterfaces();

                foreach (Type type in t2.Where( t => t.IsGenericType))
                {

                    Type[] tb = type.GetGenericArguments();

                    if (tb.Length < 2)
                        continue;

                    string s = ItemMap.FindTypeName(tb[0]);
                    string t = ItemMap.FindTypeName(tb[1]);

                    if ((s == item.SourceType || s == item.TargetType) && (t == item.SourceType || t == item.TargetType))
                    {
                        item.Cast = t1.AssemblyQualifiedName;
                        item.TypeCast = CastType.IConverter;
                        return true;
                    }

                }
            }

            return false;
        }

        public static bool FindCastIValueConverter(ItemMap item, List<Type> lst)
        {

            Type filtre = typeof(IValueConverter);

            foreach (Type type in lst.Where( c => c.IsOfType(false, filtre)))
            {
                ValueConversionAttribute attr = (ValueConversionAttribute)Attribute.GetCustomAttribute(type, typeof(ValueConversionAttribute));
                if (attr != null)
                {
                    string s = ItemMap.FindTypeName(attr.SourceType);
                    string t = ItemMap.FindTypeName(attr.TargetType);

                    if ((s == item.SourceType || s == item.TargetType) && (t == item.SourceType || t == item.TargetType))
                    {
                        item.Cast = type.AssemblyQualifiedName;
                        item.TypeCast = CastType.IValueConverter;
                        return true;
                    }
                }

            }

            return false;
        }

        /*
        public static bool FindCastTypeConverter(ItemMap item, List<Type> lst)
        {

            Mapper m = item.Mapper;
            if (m == null || m.MappingDestination == null || string.IsNullOrEmpty(m.MappingDestination.TypeElement.AssemblyQualifiedName))
                return false;

            string t = m.MappingDestination.TypeElement.AssemblyQualifiedName;
            Type t1 = lst.FirstOrDefault(c => c.AssemblyQualifiedName == t);
            if (t1 != null)
            {
                foreach (PropertyInfo p in t1.GetProperties().Where(d => d.Name == item.TargetName))
                {

                    if (p is CSharpParser.SCharpType.CsPropertyInfo)
                    {

                        var attr = Attribute.GetCustomAttribute(p, typeof(TypeConverterAttribute));


                    }
                    else
                    {
                        TypeConverterAttribute attr = (TypeConverterAttribute)Attribute.GetCustomAttribute(p, typeof(TypeConverterAttribute));
                        if (attr != null)
                        {
                            item.Cast = attr.ConverterTypeName;
                            item.TypeCast = CastType.IValueConverter;
                            return true;
                        }
                    }
                    
                }
            }
            
            return false;
        }
        */

        public static bool ResolveNeedMap(Type type)
        {

            // TODO : a revoir
            if (type.IsPrimitive || type.IsValueType || type.IsEnum || NewMapperHelper.types.Contains(type.FullName))
                return false;

            return true;
            
        }     

        private static void FindNotUsedMembers(Mapper mapper, Dictionary<string, Rank> l)
        {
            List<ItemMap> l2 = new List<ItemMap>();
            foreach (ItemMap item in mapper.ItemMaps)
                if (!string.IsNullOrEmpty(item.SourceName))
                    if (!l.ContainsKey(item.SourceName))
                        l2.Add(item);

            foreach (ItemMap item in l2)
                item.Delete();
        }


        private static void FindDestination(TypeElement right, Dictionary<string, Rank> l)
        {
            Type TypeRight = right.Type;

            if (TypeRight == null || TypeRight
                .Namespace == "System.Collections.Generic" || TypeRight.Namespace == "System.Collections")
                return;

            foreach (var item in l)
            {
                string n = item.Key;

                foreach (PropertyInfo prop in GetRecursiveProperties(TypeRight))
                    if (prop.Name == n && prop.CanWrite)
                    {
                        item.Value.Destination = prop;
                        break;
                    }

                foreach (FieldInfo prop in GetRecursiveFields(TypeRight))
                    if (prop.Name == n)
                    {
                        item.Value.Destination = prop;
                        break;
                    }

            }
        }


        private static Dictionary<string, Rank> GetSource(Mapper mapper, TypeElement left)
        {

            Dictionary<string, Rank> l = new Dictionary<string, Rank>();
            Type TypeLeft = left.Type;
            if (TypeLeft == null || TypeLeft.Namespace == "System.Collections.Generic" || TypeLeft.Namespace == "System.Collections")
                return l;

            foreach (PropertyInfo item in GetRecursiveProperties(TypeLeft))
            {

                //  comparer la liste des membres du declaring type
                MemberInfo[] m1 = item.DeclaringType.GetMembers(item.Name);
                if (m1 == null || m1.Length == 0)
                    continue;

                if (!item.CanRead || item.GetIndexParameters().Length != 0)
                    continue;

                if (!l.ContainsKey(item.Name))
                {

                    ItemMap m = FindSourceByName(mapper, item.Name);

                    bool Created = false;
                    if (m == null)
                    {
                        mapper.ItemMaps.Add((m = new ItemMap(mapper.Partition) { SourceTypeMember = MemberTypeEnum.Property }));
                        Created = true;
                    }

                    l.Add(item.Name, new Rank() { Source = item, Destination = null, Map = m, SourceCreated = Created });

                }
            }

            foreach (FieldInfo item in GetRecursiveFields(TypeLeft))
            {

                ItemMap m = FindSourceByName(mapper, item.Name);

                if (m == null)
                    mapper.ItemMaps.Add((m = new ItemMap(mapper.Partition) { SourceTypeMember = MemberTypeEnum.Field }));

                l.Add(item.Name, new Rank() { Source = item, Destination = null, Map = m });

            }

            return l;
        }


        private static IEnumerable<PropertyInfo> GetRecursiveProperties(Type type)
        {

            foreach (PropertyInfo p in type.GetProperties(BindingFlags.GetProperty | ~BindingFlags.Static | ~BindingFlags.NonPublic))
                yield return p;

            if (type.BaseType != null && type.BaseType != typeof(object))
                foreach (PropertyInfo p1 in GetRecursiveProperties(type.BaseType))
                    yield return p1;

        }
 
        private static IEnumerable<FieldInfo> GetRecursiveFields(Type type)
        {

            foreach (FieldInfo p in type.GetFields(BindingFlags.GetField | ~BindingFlags.Static))
            if (p.IsPublic)
                yield return p;

            if (type.BaseType != null && type.BaseType != typeof(object))
                foreach (FieldInfo p in GetRecursiveFields(type.BaseType))
                    yield return p;

        }

        /// <summary>
        /// Renoie un map par son nom si deja créé
        /// </summary>
        /// <param name="mapper"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private static ItemMap FindSourceByName(Mapper mapper, string name)
        {
            foreach (ItemMap ItemMap in mapper.ItemMaps)
                if (ItemMap.SourceName == name)
                    return ItemMap;

            return null;

        }


    }
}
